using System;
using System.Drawing;
using System.Drawing.Imaging;

namespace ResampleTest
{

    #region ResamplingService class

    public unsafe class ResamplingService : IPercentageProvider
    {

        // IPercentageProvider
        public bool Aborting
        {

            get
            {
                return aborting;
            }
            set
            {
                aborting = value;
            }
        }

        public float Percentage
        {

            get
            {
                return percentage;
            }
        }

        // ResamplingService
        public Bitmap Output
        {

            get
            {
                return output;
            }
        }

        struct Contributor
        {

            public int pixel;
            public double weight;
        }

        struct ContributorEntry
        {

            public int n;
            public Contributor[] p;
            public double wsum;
        }

        // IPercentageProvider
        bool aborting = false;
        float percentage = 0;
        int current = 0, total = 0; // auxiliary
        // ResamplingService
        Bitmap input = null, output = null;
        int i, j, k,
            left, right;
        double xScale, yScale, center, wdth, weight;
        ContributorEntry[] contrib;
        // filter and bitmaps
        ResamplingFilter filter;
        // direct pixel access
        BitmapData bdIn, bdOut;
        double r, g, b;
        byte* rowIn, rowOut;
        int offset;

        public ResamplingService(Bitmap input, int filterIndex)
        {

            this.input = input;

            switch (filterIndex)
            {
                case 0: filter = new BoxFilter(); break;
                case 1: filter = new TriangleFilter(); break;
                case 2: filter = new HermiteFilter(); break;
                case 3: filter = new BellFilter(); break;
                case 4: filter = new CubicBSplineFilter(); break;
                case 5: filter = new Lanczos3Filter(); break;
                case 6: filter = new MitchellFilter(); break;
                case 7: filter = new CosineFilter(); break;
                case 8: filter = new CatmullRomFilter(); break;
                case 9: filter = new QuadraticFilter(); break;
                case 10: filter = new QuadraticBSplineFilter(); break;
                case 11: filter = new CubicConvolutionFilter(); break;
                case 12: filter = new Lanczos8Filter(); break;
            }
        }

        public void Resample(int nWidth, int nHeight)
        {

            if ((input == null) || (input.Width == 0) || (input.Height == 0) || (nWidth == 0) || (nHeight == 0))
                return;

            current = 0;
            total = nWidth + input.Height;

            // set operating pixel formating
            PixelFormat pf = PixelFormat.Format24bppRgb;
            int shift = 3;

            // create intermediate image to hold horizontal zoom
            Bitmap work = new Bitmap(nWidth, input.Height, pf);

            // lock bitmaps
            bdIn = input.LockBits(new Rectangle(0, 0, input.Width, input.Height),
              ImageLockMode.ReadOnly, pf);
            bdOut = work.LockBits(new Rectangle(0, 0, nWidth, input.Height),
              ImageLockMode.ReadWrite, pf);

            xScale = (double)nWidth / (double)input.Width;
            yScale = (double)nHeight / (double)input.Height;

            contrib = new ContributorEntry[nWidth];
            // horizontal downsampling
            if (xScale < 1)
            {

                // scales from bigger to smaller width
                wdth = filter.defaultFilterRadius / xScale;
                for (i = 0; i < nWidth; i++)
                {

                    contrib[i].n = 0;
                    contrib[i].p = new Contributor[(int)Math.Floor(2 * wdth + 1)];
                    contrib[i].wsum = 0;
                    center = (i + 0.5) / xScale;
                    left = (int)(center - wdth);
                    right = (int)(center + wdth);
                    for (j = left; j <= right; j++)
                    {

                        weight = filter.GetValue((center - j - 0.5) * xScale);
                        if ((weight == 0) || (j < 0) || (j >= input.Width)) continue;
                        contrib[i].p[contrib[i].n].pixel = j;
                        contrib[i].p[contrib[i].n].weight = weight;
                        contrib[i].wsum += weight;
                        contrib[i].n++;
                    }

                    if (aborting)
                    {

                        input.UnlockBits(bdIn);
                        work.UnlockBits(bdOut);
                        goto End;
                    }
                }
            }
            else
            {

                // horizontal upsampling
                // scales from smaller to bigger width
                for (i = 0; i < nWidth; i++)
                {

                    contrib[i].n = 0;
                    contrib[i].p = new Contributor[(int)Math.Floor(2 * filter.defaultFilterRadius + 1)];
                    contrib[i].wsum = 0;
                    center = (i + 0.5) / xScale;
                    left = (int)Math.Floor(center - filter.defaultFilterRadius);
                    right = (int)Math.Ceiling(center + filter.defaultFilterRadius);
                    for (j = left; j <= right; j++)
                    {

                        weight = filter.GetValue(center - j - 0.5);
                        if ((weight == 0) || (j < 0) || (j >= input.Width)) continue;
                        contrib[i].p[contrib[i].n].pixel = j;
                        contrib[i].p[contrib[i].n].weight = weight;
                        contrib[i].wsum += weight;
                        contrib[i].n++;

                    }

                    if (aborting)
                    {

                        input.UnlockBits(bdIn);
                        work.UnlockBits(bdOut);
                        goto End;
                    }
                }
            }

            // filter horizontally from input to work
            for (k = 0; k < input.Height; k++)
            {

                rowIn = (byte*)bdIn.Scan0 + k * bdIn.Stride;
                rowOut = (byte*)bdOut.Scan0 + k * bdOut.Stride;
                for (i = 0; i < nWidth; i++)
                {

                    r = g = b = 0;
                    for (j = 0; j < contrib[i].n; j++)
                    {

                        offset = contrib[i].p[j].pixel * shift;
                        weight = contrib[i].p[j].weight;
                        if (weight == 0) continue;
                        if (shift == 3)
                        {

                            r += rowIn[offset + 2] * weight;
                            g += rowIn[offset + 1] * weight;
                        }
                        b += rowIn[offset] * weight;
                    }
                    if (shift == 3)
                    {

                        r /= contrib[i].wsum;
                        g /= contrib[i].wsum;
                        if (r > 255) rowOut[2] = 255; else if (r < 0) rowOut[2] = 0; else rowOut[2] = (byte)r;
                        if (g > 255) rowOut[1] = 255; else if (g < 0) rowOut[1] = 0; else rowOut[1] = (byte)g;
                    }
                    b /= contrib[i].wsum;
                    if (b > 255) rowOut[0] = 255; else if (b < 0) rowOut[0] = 0; else rowOut[0] = (byte)b;
                    rowOut += shift;
                }

                if (aborting)
                {

                    input.UnlockBits(bdIn);
                    work.UnlockBits(bdOut);
                    goto End;
                }
                current++;
                percentage = (float)current / total * 100;
            }

            // create final output image
            output = new Bitmap(nWidth, nHeight, pf);

            // unlock input and lock output bitmap
            input.UnlockBits(bdIn);
            bdIn = bdOut;
            bdOut = output.LockBits(new Rectangle(0, 0, nWidth, nHeight), ImageLockMode.WriteOnly, pf);

            // pre-calculate filter contributions for a column
            contrib = new ContributorEntry[nHeight];
            // vertical downsampling
            if (yScale < 1)
            {

                // scales from bigger to smaller height
                wdth = filter.defaultFilterRadius / yScale;
                for (i = 0; i < nHeight; i++)
                {

                    contrib[i].n = 0;
                    contrib[i].p = new Contributor[(int)Math.Floor(2 * wdth + 1)];
                    contrib[i].wsum = 0;
                    center = (i + 0.5) / yScale;
                    left = (int)(center - wdth);
                    right = (int)(center + wdth);
                    for (j = left; j <= right; j++)
                    {

                        weight = filter.GetValue((center - j - 0.5) * yScale);
                        if ((weight == 0) || (j < 0) || (j >= input.Height)) continue;
                        contrib[i].p[contrib[i].n].pixel = j;
                        contrib[i].p[contrib[i].n].weight = weight;
                        contrib[i].wsum += weight;
                        contrib[i].n++;
                    }

                    if (aborting)
                    {

                        work.UnlockBits(bdIn);
                        output.UnlockBits(bdOut);
                        goto End;
                    }
                }
            }
            else
            {

                // vertical upsampling
                // scales from smaller to bigger height
                for (i = 0; i < nHeight; i++)
                {

                    contrib[i].n = 0;
                    contrib[i].p = new Contributor[(int)Math.Floor(2 * filter.defaultFilterRadius + 1)];
                    contrib[i].wsum = 0;
                    center = (i + 0.5) / yScale;
                    left = (int)(center - filter.defaultFilterRadius);
                    right = (int)(center + filter.defaultFilterRadius);
                    for (j = left; j <= right; j++)
                    {

                        weight = filter.GetValue(center - j - 0.5);
                        if ((weight == 0) || (j < 0) || (j >= input.Height)) continue;
                        contrib[i].p[contrib[i].n].pixel = j;
                        contrib[i].p[contrib[i].n].weight = weight;
                        contrib[i].wsum += weight;
                        contrib[i].n++;
                    }

                    if (aborting)
                    {

                        work.UnlockBits(bdIn);
                        output.UnlockBits(bdOut);
                        goto End;
                    }
                }
            }

            // filter vertically from work to output
            for (k = 0; k < nWidth; k++)
            {
                for (i = 0; i < nHeight; i++)
                {

                    r = g = b = 0;
                    for (j = 0; j < contrib[i].n; j++)
                    {

                        rowIn = (byte*)bdIn.Scan0 + contrib[i].p[j].pixel * bdIn.Stride + k * shift;
                        weight = contrib[i].p[j].weight;
                        if (weight == 0) continue;
                        if (shift == 3)
                        {

                            r += rowIn[2] * weight;
                            g += rowIn[1] * weight;
                        }
                        b += rowIn[0] * weight;
                    }
                    rowOut = (byte*)bdOut.Scan0 + i * bdOut.Stride + k * shift;
                    if (shift == 3)
                    {

                        r /= contrib[i].wsum;
                        g /= contrib[i].wsum;
                        if (r > 255) rowOut[2] = 255; else if (r < 0) rowOut[2] = 0; else rowOut[2] = (byte)r;
                        if (g > 255) rowOut[1] = 255; else if (g < 0) rowOut[1] = 0; else rowOut[1] = (byte)g;
                    }
                    b /= contrib[i].wsum;
                    if (b > 255) rowOut[0] = 255; else if (b < 0) rowOut[0] = 0; else rowOut[0] = (byte)b;
                }

                if (aborting)
                {

                    work.UnlockBits(bdIn);
                    output.UnlockBits(bdOut);
                    goto End;
                }
                current++;
                percentage = (float)current / total * 100;
            }

            // unlock bitmaps
            work.UnlockBits(bdIn);
            output.UnlockBits(bdOut);

        End: ;

            filter = null;
        }
    }

    #endregion

    #region ResamplingFilter class

    abstract class ResamplingFilter
    {

        public double defaultFilterRadius;
        public abstract double GetValue(double x);
    }

    class HermiteFilter : ResamplingFilter
    {

        public HermiteFilter()
        {

            defaultFilterRadius = 1;
        }

        public override double GetValue(double x)
        {

            if (x < 0) x = -x;
            if (x < 1) return ((2 * x - 3) * x * x + 1);
            return 0;
        }
    }

    class BoxFilter : ResamplingFilter
    {

        public BoxFilter()
        {

            defaultFilterRadius = 0.5;
        }

        public override double GetValue(double x)
        {

            if (x < 0) x = -x;
            if (x <= 0.5) return 1;
            return 0;
        }
    }

    class TriangleFilter : ResamplingFilter
    {

        public TriangleFilter()
        {

            defaultFilterRadius = 1;
        }

        public override double GetValue(double x)
        {

            if (x < 0) x = -x;
            if (x < 1) return (1 - x);
            return 0;
        }
    }

    class BellFilter : ResamplingFilter
    {

        public BellFilter()
        {

            defaultFilterRadius = 1.5;
        }

        public override double GetValue(double x)
        {

            if (x < 0) x = -x;
            if (x < 0.5) return (0.75 - x * x);
            if (x < 1.5) return (0.5 * Math.Pow(x - 1.5, 2));
            return 0;
        }
    }

    class CubicBSplineFilter : ResamplingFilter
    {

        double temp;

        public CubicBSplineFilter()
        {

            defaultFilterRadius = 2;
        }

        public override double GetValue(double x)
        {

            if (x < 0) x = -x;
            if (x < 1)
            {

                temp = x * x;
                return (0.5 * temp * x - temp + 2f / 3f);
            }
            if (x < 2)
            {

                x = 2f - x;
                return (Math.Pow(x, 3) / 6f);
            }
            return 0;
        }
    }

    class Lanczos3Filter : ResamplingFilter
    {

        public Lanczos3Filter()
        {

            defaultFilterRadius = 3;
        }

        double SinC(double x)
        {

            if (x != 0)
            {

                x *= Math.PI;
                return (Math.Sin(x) / x);
            }
            return 1;
        }

        public override double GetValue(double x)
        {

            if (x < 0) x = -x;
            if (x < 3) return (SinC(x) * SinC(x / 3f));
            return 0;
        }
    }

    class MitchellFilter : ResamplingFilter
    {

        const double C = 1 / 3;
        double temp;

        public MitchellFilter()
        {

            defaultFilterRadius = 2;
        }

        public override double GetValue(double x)
        {

            if (x < 0) x = -x;
            temp = x * x;
            if (x < 1)
            {

                x = (((12 - 9 * C - 6 * C) * (x * temp)) + ((-18 + 12 * C + 6 * C) * temp) + (6 - 2 * C));
                return (x / 6);
            }
            if (x < 2)
            {

                x = (((-C - 6 * C) * (x * temp)) + ((6 * C + 30 * C) * temp) + ((-12 * C - 48 * C) * x) + (8 * C + 24 * C));
                return (x / 6);
            }
            return 0;
        }
    }

    class CosineFilter : ResamplingFilter
    {

        public CosineFilter()
        {

            defaultFilterRadius = 1;
        }

        public override double GetValue(double x)
        {

            if ((x >= -1) && (x <= 1)) return ((Math.Cos(x * Math.PI) + 1) / 2f);
            return 0;
        }
    }

    class CatmullRomFilter : ResamplingFilter
    {

        const double C = 1 / 2;
        double temp;

        public CatmullRomFilter()
        {

            defaultFilterRadius = 2;
        }

        public override double GetValue(double x)
        {

            if (x < 0) x = -x;
            temp = x * x;
            if (x <= 1) return (1.5 * temp * x - 2.5 * temp + 1);
            if (x <= 2) return (-0.5 * temp * x + 2.5 * temp - 4 * x + 2);
            return 0;
        }
    }

    class QuadraticFilter : ResamplingFilter
    {

        public QuadraticFilter()
        {

            defaultFilterRadius = 1.5;
        }

        public override double GetValue(double x)
        {

            if (x < 0) x = -x;
            if (x <= 0.5) return (-2 * x * x + 1);
            if (x <= 1.5) return (x * x - 2.5 * x + 1.5);
            return 0;
        }
    }

    class QuadraticBSplineFilter : ResamplingFilter
    {

        public QuadraticBSplineFilter()
        {

            defaultFilterRadius = 1.5;
        }

        public override double GetValue(double x)
        {

            if (x < 0) x = -x;
            if (x <= 0.5) return (-x * x + 0.75);
            if (x <= 1.5) return (0.5 * x * x - 1.5 * x + 1.125);
            return 0;
        }
    }

    class CubicConvolutionFilter : ResamplingFilter
    {

        double temp;

        public CubicConvolutionFilter()
        {

            defaultFilterRadius = 3;
        }

        public override double GetValue(double x)
        {

            if (x < 0) x = -x;
            temp = x * x;
            if (x <= 1) return ((4f / 3f) * temp * x - (7f / 3f) * temp + 1);
            if (x <= 2) return (-(7f / 12f) * temp * x + 3 * temp - (59f / 12f) * x + 2.5);
            if (x <= 3) return ((1f / 12f) * temp * x - (2f / 3f) * temp + 1.75 * x - 1.5);
            return 0;
        }
    }

    class Lanczos8Filter : ResamplingFilter
    {

        public Lanczos8Filter()
        {

            defaultFilterRadius = 8;
        }

        double SinC(double x)
        {

            if (x != 0)
            {

                x *= Math.PI;
                return (Math.Sin(x) / x);
            }
            return 1;
        }

        public override double GetValue(double x)
        {

            if (x < 0) x = -x;
            if (x < 8) return (SinC(x) * SinC(x / 8f));
            return 0;
        }
    }

    #endregion
}
namespace ResampleTest
{

    public interface IPercentageProvider
    {

        bool Aborting { get; set; }
        float Percentage { get; }
    }
}